/*
 * ReadGraph.java April 2007
 *
 * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package wx.xml.simpleframework.xml.strategy;

import java.util.HashMap;

import wx.xml.simpleframework.xml.stream.Node;
import wx.xml.simpleframework.xml.stream.NodeMap;

/**
 * The <code>ReadGraph</code> object is used to build a graph of the
 * objects that have been deserialized from the XML document. This is
 * required so that cycles in the object graph can be recreated such
 * that the deserialized object is an exact duplicate of the object
 * that was serialized. Objects are stored in the graph using unique
 * keys, which for this implementation are unique strings.
 *
 * @author Niall Gallagher
 * @see wx.xml.simpleframework.xml.strategy.WriteGraph
 */
class ReadGraph extends HashMap {

    /**
     * This is the class loader that is used to load the types used.
     */
    private final Loader loader;

    /**
     * This is used to represent the length of array object values.
     */
    private final String length;

    /**
     * This is the label used to mark the type of an object.
     */
    private final String label;

    /**
     * This is the attribute used to mark the identity of an object.
     */
    private final String mark;

    /**
     * This is the attribute used to refer to an existing instance.
     */
    private final String refer;

    /**
     * Constructor for the <code>ReadGraph</code> object. This is used
     * to create graphs that are used for reading objects from the XML
     * document. The specified strategy is used to acquire the names
     * of the special attributes used during the serialization.
     *
     * @param contract this is the name scheme used by the strategy
     * @param loader   this is the class loader to used for the graph
     */
    public ReadGraph(Contract contract, Loader loader) {
        this.refer = contract.getReference();
        this.mark = contract.getIdentity();
        this.length = contract.getLength();
        this.label = contract.getLabel();
        this.loader = loader;
    }

    /**
     * This is used to recover the object references from the document
     * using the special attributes specified. This allows the element
     * specified by the <code>NodeMap</code> to be used to discover
     * exactly which node in the object graph the element represents.
     *
     * @param type the type of the field or method in the instance
     * @param node this is the XML element to be deserialized
     * @return this is used to return the type to acquire the value
     */
    public Value read(Type type, NodeMap node) throws Exception {
        Node  entry  = node.remove(label);
        Class expect = type.getType();

        if (expect.isArray()) {
            expect = expect.getComponentType();
        }
        if (entry != null) {
            String name = entry.getValue();
            expect = loader.load(name);
        }
        return readInstance(type, expect, node);
    }

    /**
     * This is used to recover the object references from the document
     * using the special attributes specified. This allows the element
     * specified by the <code>NodeMap</code> to be used to discover
     * exactly which node in the object graph the element represents.
     *
     * @param type the type of the field or method in the instance
     * @param real this is the overridden type from the XML element
     * @param node this is the XML element to be deserialized
     * @return this is used to return the type to acquire the value
     */
    private Value readInstance(Type type, Class real, NodeMap node) throws Exception {
        Node entry = node.remove(mark);

        if (entry == null) {
            return readReference(type, real, node);
        }
        String key = entry.getValue();

        if (containsKey(key)) {
            throw new CycleException("Element '%s' already exists", key);
        }
        return readValue(type, real, node, key);
    }

    /**
     * This is used to recover the object references from the document
     * using the special attributes specified. This allows the element
     * specified by the <code>NodeMap</code> to be used to discover
     * exactly which node in the object graph the element represents.
     *
     * @param type the type of the field or method in the instance
     * @param real this is the overridden type from the XML element
     * @param node this is the XML element to be deserialized
     * @return this is used to return the type to acquire the value
     */
    private Value readReference(Type type, Class real, NodeMap node) throws Exception {
        Node entry = node.remove(refer);

        if (entry == null) {
            return readValue(type, real, node);
        }
        String key   = entry.getValue();
        Object value = get(key);

        if (!containsKey(key)) {
            throw new CycleException("Invalid reference '%s' found", key);
        }
        return new Reference(value, real);
    }

    /**
     * This is used to acquire the <code>Value</code> which can be used
     * to represent the deserialized value. The type create cab be
     * added to the graph of created instances if the XML element has
     * an identification attribute, this allows cycles to be completed.
     *
     * @param type the type of the field or method in the instance
     * @param real this is the overridden type from the XML element
     * @param node this is the XML element to be deserialized
     * @return this is used to return the type to acquire the value
     */
    private Value readValue(Type type, Class real, NodeMap node) throws Exception {
        Class expect = type.getType();

        if (expect.isArray()) {
            return readArray(type, real, node);
        }
        return new ObjectValue(real);
    }

    /**
     * This is used to acquire the <code>Value</code> which can be used
     * to represent the deserialized value. The type create cab be
     * added to the graph of created instances if the XML element has
     * an identification attribute, this allows cycles to be completed.
     *
     * @param type the type of the field or method in the instance
     * @param real this is the overridden type from the XML element
     * @param node this is the XML element to be deserialized
     * @param key  the key the instance is known as in the graph
     * @return this is used to return the type to acquire the value
     */
    private Value readValue(Type type, Class real, NodeMap node, String key) throws Exception {
        Value value = readValue(type, real, node);

        if (key != null) {
            return new Allocate(value, this, key);
        }
        return value;
    }

    /**
     * This is used to acquire the <code>Value</code> which can be used
     * to represent the deserialized value. The type create cab be
     * added to the graph of created instances if the XML element has
     * an identification attribute, this allows cycles to be completed.
     *
     * @param type the type of the field or method in the instance
     * @param real this is the overridden type from the XML element
     * @param node this is the XML element to be deserialized
     * @return this is used to return the type to acquire the value
     */
    private Value readArray(Type type, Class real, NodeMap node) throws Exception {
        Node entry = node.remove(length);
        int  size  = 0;

        if (entry != null) {
            String value = entry.getValue();
            size = Integer.parseInt(value);
        }
        return new ArrayValue(real, size);
    }
}
